﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Microsoft.Surface.Presentation;
using System.Windows.Shapes;
using Microsoft.Surface.Presentation.Controls;

namespace WPFMitsuControlsv2
{
    [TemplatePart(Name = BookPage.ElementPage0, Type = typeof(ContentPresenter))]
    [TemplatePart(Name = BookPage.ElementPage1, Type = typeof(ContentPresenter))]
    [TemplatePart(Name = BookPage.ElementPage2, Type = typeof(ContentPresenter))]
    [TemplatePart(Name = BookPage.ElementClippingPage0, Type = typeof(CombinedGeometry))]
    [TemplatePart(Name = BookPage.ElementGridShadow, Type = typeof(Canvas))]
    [TemplatePart(Name = BookPage.ElementPageShadow, Type = typeof(LinearGradientBrush))]
    [TemplatePart(Name = BookPage.ElementRectangleTranslate, Type = typeof(TranslateTransform))]
    [TemplatePart(Name = BookPage.ElementRectangleRotate, Type = typeof(RotateTransform))]
    [TemplatePart(Name = BookPage.ElementClippingFigure, Type = typeof(PathGeometry))]
    [TemplatePart(Name = BookPage.ElementCanvasReflection, Type = typeof(Canvas))]
    [TemplatePart(Name = BookPage.ElementPageReflection, Type = typeof(LinearGradientBrush))]
    public partial class BookPage : SurfaceContentControl
    {

        #region Constants

        private const int animationDuration = 500;

        private const string ElementPage0 = "PART_Page0";
        private const string ElementPage1 = "PART_Page1";
        private const string ElementPage2 = "PART_Page2";
        private const string ElementClippingPage0 = "PART_ClippingPage0";
        private const string ElementGridShadow = "PART_GridShadow";
        private const string ElementPageShadow = "PART_PageShadow";
        private const string ElementRectangleTranslate = "PART_RectangleTranslate";
        private const string ElementRectangleRotate = "PART_RectangleRotate";
        private const string ElementClippingFigure = "PART_ClippingFigure";
        private const string ElementCanvasReflection = "PART_CanvasReflection";
        private const string ElementPageReflection = "PART_PageReflection";

        #endregion Constants

        #region Private vars

        private Contact _currentDraggingContact;
        private Point _cornerPoint;
        private PageStatus _status = PageStatus.None;

        private ContentPresenter _page0;
        private ContentPresenter _page1;
        private ContentPresenter _page2;

        private CombinedGeometry _clippingPage0;
        private Canvas _gridShadow;
        private LinearGradientBrush _pageShadow;
        private TranslateTransform _rectangleTranslate;
        private RotateTransform _rectangleRotate;
        private PathGeometry _clippingFigure;
        private Canvas _canvasReflection;
        private LinearGradientBrush _pageReflection;

        #endregion

        #region Internal vars

        internal CornerOrigin origin = CornerOrigin.BottomRight;
        internal Action<PageStatus> SetStatus = null;
        internal Read<PageStatus> GetStatus = null;

        #endregion

        #region Constructors

        public BookPage()
        {
            this.Loaded += OnLoaded;
        }

        static BookPage()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(BookPage), new FrameworkPropertyMetadata(typeof(BookPage)));

            PageTurnedEvent = EventManager.RegisterRoutedEvent("PageTurned", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BookPage));
            CornerPointProperty = DependencyProperty.Register("CornerPoint", typeof(Point), typeof(BookPage));
            IsTopLeftCornerEnabledProperty = DependencyProperty.Register("IsTopLeftCornerEnabled", typeof(bool), typeof(BookPage), new PropertyMetadata(true));
            IsTopRightCornerEnabledProperty = DependencyProperty.Register("IsTopRightCornerEnabled", typeof(bool), typeof(BookPage), new PropertyMetadata(true));
            IsBottomLeftCornerEnabledProperty = DependencyProperty.Register("IsBottomLeftCornerEnabled", typeof(bool), typeof(BookPage), new PropertyMetadata(true));
            IsBottomRightCornerEnabledProperty = DependencyProperty.Register("IsBottomRightCornerEnabled", typeof(bool), typeof(BookPage), new PropertyMetadata(true));
        }

        #endregion

        #region Public properties

        public static DependencyProperty CornerPointProperty;
        public static DependencyProperty IsTopLeftCornerEnabledProperty;
        public static DependencyProperty IsTopRightCornerEnabledProperty;
        public static DependencyProperty IsBottomLeftCornerEnabledProperty;
        public static DependencyProperty IsBottomRightCornerEnabledProperty;

        public bool IsTopLeftCornerEnabled
        {
            get { return (bool)GetValue(BookPage.IsTopLeftCornerEnabledProperty); }
            set { SetValue(BookPage.IsTopLeftCornerEnabledProperty, value); }
        }
        public bool IsTopRightCornerEnabled
        {
            get { return (bool)GetValue(BookPage.IsTopRightCornerEnabledProperty); }
            set { SetValue(BookPage.IsTopRightCornerEnabledProperty, value); }
        }
        public bool IsBottomLeftCornerEnabled
        {
            get { return (bool)GetValue(BookPage.IsBottomLeftCornerEnabledProperty); }
            set { SetValue(BookPage.IsBottomLeftCornerEnabledProperty, value); }
        }
        public bool IsBottomRightCornerEnabled
        {
            get { return (bool)GetValue(BookPage.IsBottomRightCornerEnabledProperty); }
            set { SetValue(BookPage.IsBottomRightCornerEnabledProperty, value); }
        }

        public PageStatus Status
        {
            private get
            {
                if (GetStatus != null)
                    return GetStatus();
                else
                    return _status;
            }
            set
            {
                if (SetStatus != null)
                    SetStatus(value);
                else
                    _status = value;

                _canvasReflection.Visibility = value == PageStatus.None ? Visibility.Hidden : Visibility.Visible;
                _gridShadow.Visibility = value == PageStatus.None ? Visibility.Hidden : Visibility.Visible;

                // self set busy if the book doesn't do it
                if (SetStatus == null)
                    IsBookBusy = value != PageStatus.None;
            }
        }

        private Point CornerPoint
        {
            get { return (Point)GetValue(BookPage.CornerPointProperty); }
            set { SetValue(BookPage.CornerPointProperty, value); }
        }

        public bool IsBookBusy { get; set; }

        public double GripSize
        {
            get { return (double)GetValue(GripSizeProperty); }
            set { SetValue(GripSizeProperty, value); }
        }

        public static readonly DependencyProperty GripSizeProperty =
            DependencyProperty.Register("GripSize", typeof(double), typeof(BookPage), new UIPropertyMetadata(Book.DefaultGripSize));

        public bool CanDragOutOfPage
        {
            get { return (bool)GetValue(CanDragOutOfPageProperty); }
            set { SetValue(CanDragOutOfPageProperty, value); }
        }
        public static readonly DependencyProperty CanDragOutOfPageProperty =
            DependencyProperty.Register("CanDragOutOfPage", typeof(bool), typeof(BookPage), new UIPropertyMetadata(true));

        public double? DragAngle
        {
            get { return (double?)GetValue(DragAngleProperty); }
            set { SetValue(DragAngleProperty, value); }
        }
        public static readonly DependencyProperty DragAngleProperty =
            DependencyProperty.Register("DragAngle", typeof(double?), typeof(BookPage), new UIPropertyMetadata(null));

        public ContentPresenter Page0ContentPresenter
        {
            get { return _page0; }
        }

        public ContentPresenter Page1ContentPresenter
        {
            get { return _page1; }
        }

        public ContentPresenter Page2ContentPresenter
        {
            get { return _page2; }
        }

        #region Page0

        public object Page0Content
        {
            get { return (object)GetValue(Page0ContentProperty); }
            set { SetValue(Page0ContentProperty, value); }
        }

        public DataTemplate Page0ContentTemplate
        {
            get { return (DataTemplate)base.GetValue(Page0ContentTemplateProperty); }
            set { base.SetValue(Page0ContentTemplateProperty, value); }
        }

        public DataTemplateSelector Page0ContentTemplateSelector
        {
            get { return (DataTemplateSelector)base.GetValue(Page0ContentTemplateSelectorProperty); }
            set { base.SetValue(Page0ContentTemplateSelectorProperty, value); }
        }

        public string Page0ContentStringFormat
        {
            get { return (string)base.GetValue(Page0ContentStringFormatProperty); }
            set { base.SetValue(Page0ContentStringFormatProperty, value); }
        }

        public Brush Page0Background
        {
            get { return (Brush)GetValue(Page0BackgroundProperty); }
            set { SetValue(Page0BackgroundProperty, value); }
        }

        public static readonly DependencyProperty Page0ContentProperty =
            DependencyProperty.Register("Page0Content", typeof(object), typeof(BookPage), new UIPropertyMetadata(null));

        public static readonly DependencyProperty Page0ContentTemplateProperty =
            DependencyProperty.Register("Page0ContentTemplate",
            typeof(DataTemplate), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page0ContentTemplateSelectorProperty =
            DependencyProperty.Register("Page0ContentTemplateSelector",
            typeof(DataTemplateSelector), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page0ContentStringFormatProperty =
            DependencyProperty.Register("Page0ContentStringFormat", typeof(string), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page0BackgroundProperty =
            DependencyProperty.Register("Page0Background", typeof(Brush), typeof(BookPage), new UIPropertyMetadata(null));

        #endregion

        #region Page1

        public object Page1Content
        {
            get { return (object)GetValue(Page1ContentProperty); }
            set { SetValue(Page1ContentProperty, value); }
        }

        public DataTemplate Page1ContentTemplate
        {
            get { return (DataTemplate)base.GetValue(Page1ContentTemplateProperty); }
            set { base.SetValue(Page1ContentTemplateProperty, value); }
        }

        public DataTemplateSelector Page1ContentTemplateSelector
        {
            get { return (DataTemplateSelector)base.GetValue(Page1ContentTemplateSelectorProperty); }
            set { base.SetValue(Page1ContentTemplateSelectorProperty, value); }
        }

        public string Page1ContentStringFormat
        {
            get { return (string)base.GetValue(Page1ContentStringFormatProperty); }
            set { base.SetValue(Page1ContentStringFormatProperty, value); }
        }

        public Brush Page1Background
        {
            get { return (Brush)GetValue(Page1BackgroundProperty); }
            set { SetValue(Page1BackgroundProperty, value); }
        }

        public static readonly DependencyProperty Page1ContentProperty =
            DependencyProperty.Register("Page1Content", typeof(object), typeof(BookPage), new UIPropertyMetadata(null));

        public static readonly DependencyProperty Page1ContentTemplateProperty =
            DependencyProperty.Register("Page1ContentTemplate",
            typeof(DataTemplate), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page1ContentTemplateSelectorProperty =
            DependencyProperty.Register("Page1ContentTemplateSelector",
            typeof(DataTemplateSelector), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page1ContentStringFormatProperty =
            DependencyProperty.Register("Page1ContentStringFormat", typeof(string), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page1BackgroundProperty =
            DependencyProperty.Register("Page1Background", typeof(Brush), typeof(BookPage), new UIPropertyMetadata(null));

        #endregion

        #region Page2

        public object Page2Content
        {
            get { return (object)GetValue(Page2ContentProperty); }
            set { SetValue(Page2ContentProperty, value); }
        }

        public DataTemplate Page2ContentTemplate
        {
            get { return (DataTemplate)base.GetValue(Page2ContentTemplateProperty); }
            set { base.SetValue(Page2ContentTemplateProperty, value); }
        }

        public DataTemplateSelector Page2ContentTemplateSelector
        {
            get { return (DataTemplateSelector)base.GetValue(Page2ContentTemplateSelectorProperty); }
            set { base.SetValue(Page2ContentTemplateSelectorProperty, value); }
        }

        public string Page2ContentStringFormat
        {
            get { return (string)base.GetValue(Page2ContentStringFormatProperty); }
            set { base.SetValue(Page2ContentStringFormatProperty, value); }
        }

        public Brush Page2Background
        {
            get { return (Brush)GetValue(Page2BackgroundProperty); }
            set { SetValue(Page2BackgroundProperty, value); }
        }

        public static readonly DependencyProperty Page2ContentProperty =
            DependencyProperty.Register("Page2Content", typeof(object), typeof(BookPage), new UIPropertyMetadata(null));

        public static readonly DependencyProperty Page2ContentTemplateProperty =
            DependencyProperty.Register("Page2ContentTemplate",
            typeof(DataTemplate), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page2ContentTemplateSelectorProperty =
            DependencyProperty.Register("Page2ContentTemplateSelector",
            typeof(DataTemplateSelector), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page2ContentStringFormatProperty =
            DependencyProperty.Register("Page2ContentStringFormat", typeof(string), typeof(BookPage), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty Page2BackgroundProperty =
            DependencyProperty.Register("Page2Background", typeof(Brush), typeof(BookPage), new UIPropertyMetadata(null));

        #endregion

        #endregion

        #region Events

        public static readonly RoutedEvent PageTurnedEvent;

        public event RoutedEventHandler PageTurned
        {
            add
            {
                base.AddHandler(PageTurnedEvent, value);
            }
            remove
            {
                base.RemoveHandler(PageTurnedEvent, value);
            }
        }

        #endregion

        #region Overrides

        public override void OnApplyTemplate()
        {

            _page0 = GetTemplateChild(BookPage.ElementPage0) as ContentPresenter;
            _page1 = GetTemplateChild(BookPage.ElementPage1) as ContentPresenter;
            _page2 = GetTemplateChild(BookPage.ElementPage2) as ContentPresenter;

            _clippingPage0 = GetTemplateChild(BookPage.ElementClippingPage0) as CombinedGeometry;
            _gridShadow = GetTemplateChild(BookPage.ElementGridShadow) as Canvas;
            _pageShadow = GetTemplateChild(BookPage.ElementPageShadow) as LinearGradientBrush;
            _rectangleTranslate = GetTemplateChild(BookPage.ElementRectangleTranslate) as TranslateTransform;
            _rectangleRotate = GetTemplateChild(BookPage.ElementRectangleRotate) as RotateTransform;
            _clippingFigure = GetTemplateChild(BookPage.ElementClippingFigure) as PathGeometry;
            _canvasReflection = GetTemplateChild(BookPage.ElementCanvasReflection) as Canvas;
            _pageReflection = GetTemplateChild(BookPage.ElementPageReflection) as LinearGradientBrush;
        }

        #endregion

        private void OnLoaded(object sender, RoutedEventArgs args)
        {
            if (RenderSize != new Size(0, 0))
                ApplyParameters(new PageParameters(this.RenderSize));
        }

        void anim_Completed(object sender, EventArgs e)
        {
            //Application.Current.MainWindow.Title += "C";

            ApplyParameters(new PageParameters(this.RenderSize));

            if (Status == PageStatus.TurnAnimation)
            {
                Status = PageStatus.None;
                RaiseEvent(new RoutedEventArgs(BookPage.PageTurnedEvent, this));
            }
            else
                Status = PageStatus.None;
        }

        void anim_CurrentTimeInvalidated(object sender, EventArgs e)
        {
            //Application.Current.MainWindow.Title += "I";
            PageParameters? parameters = ComputePage(this, CornerPoint, origin);
            _cornerPoint = CornerPoint;
            if (parameters != null)
                ApplyParameters(parameters.Value);
        }

        private void ApplyParameters(PageParameters parameters)
        {
            _pageReflection.Opacity = parameters.Page0ShadowOpacity;

            _rectangleRotate.Angle = parameters.Page1RotateAngle;
            _rectangleRotate.CenterX = parameters.Page1RotateCenterX;
            _rectangleRotate.CenterY = parameters.Page1RotateCenterY;
            _rectangleTranslate.X = parameters.Page1TranslateX;
            _rectangleTranslate.Y = parameters.Page1TranslateY;
            _clippingFigure.Figures.Clear();
            _clippingFigure.Figures.Add(parameters.Page1ClippingFigure);

            RectangleGeometry rg = (RectangleGeometry)_clippingPage0.Geometry1;
            rg.Rect = new Rect(parameters.RenderSize);
            PathGeometry pg = (PathGeometry)_clippingPage0.Geometry2;
            pg.Figures.Clear();
            pg.Figures.Add(parameters.Page2ClippingFigure);

            _pageReflection.StartPoint = parameters.Page1ReflectionStartPoint;
            _pageReflection.EndPoint = parameters.Page1ReflectionEndPoint;

            _pageShadow.StartPoint = parameters.Page0ShadowStartPoint;
            _pageShadow.EndPoint = parameters.Page0ShadowEndPoint;
        }

        private static int ComputeAnimationDuration(UIElement source, Point p, CornerOrigin origin)
        {
            double ratio = ComputeProgressRatio(source, p, origin);

            return Convert.ToInt32(animationDuration * (ratio / 2 + 0.5));
        }

        private static double ComputeProgressRatio(UIElement source, Point p, CornerOrigin origin)
        {
            if ((origin == CornerOrigin.BottomLeft) || (origin == CornerOrigin.TopLeft))
                return p.X / source.RenderSize.Width;
            else
                return (source.RenderSize.Width - p.X) / source.RenderSize.Width;
        }

        private CornerOrigin? GetCorner(UIElement source, Point position)
        {
            CornerOrigin? result = null;

            Rect topLeftRectangle = new Rect(0, 0, GripSize, GripSize);
            Rect topRightRectangle = new Rect(source.RenderSize.Width - GripSize, 0, GripSize, GripSize);
            Rect bottomLeftRectangle = new Rect(0, source.RenderSize.Height - GripSize, GripSize, GripSize);
            Rect bottomRightRectangle = new Rect(source.RenderSize.Width - GripSize, source.RenderSize.Height - GripSize, GripSize, GripSize);

            if (IsTopLeftCornerEnabled && topLeftRectangle.Contains(position))
                result = CornerOrigin.TopLeft;
            else if (IsTopRightCornerEnabled && topRightRectangle.Contains(position))
                result = CornerOrigin.TopRight;
            else if (IsBottomLeftCornerEnabled && bottomLeftRectangle.Contains(position))
                result = CornerOrigin.BottomLeft;
            else if (IsBottomRightCornerEnabled && bottomRightRectangle.Contains(position))
                result = CornerOrigin.BottomRight;

            return result;
        }



        #region Mouse events overrides

        protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            OnMouseOrContactDown(this, e);
            base.OnMouseDown(e);
        }

        protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
        {
            OnMouseOrContactMove(this, e);
            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            OnMouseOrContactUp(this, e);
            base.OnMouseUp(e);
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            OnMouseOrContactLeave(this, e);
            base.OnMouseLeave(e);
        }

        #endregion

        #region Surface events overrides

        protected override void OnContactDown(ContactEventArgs e)
        {
            OnMouseOrContactDown(this, e);
            base.OnContactDown(e);

            if (e.Contact.Captured == this)
                e.Handled = true;
        }

        protected override void OnContactChanged(ContactEventArgs e)
        {
            base.OnContactChanged(e);

            OnMouseOrContactMove(this, e);

            if (e.Contact.Captured == this)
                e.Handled = true;

        }

        protected override void OnContactUp(ContactEventArgs e)
        {
            base.OnContactUp(e);

            OnMouseOrContactUp(this, e);

            if (e.Contact.Captured == this)
            {
                e.Handled = true;
                e.Contact.Capture(null);
            }

            e.Handled = true;
        }

        protected override void OnContactLeave(ContactEventArgs e)
        {
            OnMouseOrContactLeave(this, e);
            base.OnContactLeave(e);
            e.Handled = true;
        }

        #endregion

        #region Mouse & contacts

        private void OnMouseOrContactMove(object sender, EventArgs args)
        {
            if ((Status == PageStatus.DropAnimation) || (Status == PageStatus.TurnAnimation))
                return;

            //Application.Current.MainWindow.Title += "M";

            UIElement source = sender as UIElement;

            bool isContact = args is ContactEventArgs;

            Point p = new Point();
            if (isContact)
            {
                bool isContactCaptured = args is ContactEventArgs ? ((ContactEventArgs)args).Contact.Captured == this : false;
                if (!isContactCaptured)
                    return;

                // SURFACE
                var contactArgs = (ContactEventArgs)args;
                p = contactArgs.GetPosition(source);
                _currentDraggingContact = contactArgs.Contact;
            }
            else if (args is MouseButtonEventArgs)
            {
                p = ((MouseButtonEventArgs)args).GetPosition(source);
            }

            if (!isContact && !(sender as UIElement).IsMouseCaptured)
            {
                CornerOrigin? tmp = GetCorner(source, p);

                if (tmp.HasValue)
                    origin = tmp.Value;
                else
                {
                    if (Status == PageStatus.DraggingWithoutCapture)
                        DropPage(ComputeAnimationDuration(source, p, origin));

                    return;
                }
                Status = PageStatus.DraggingWithoutCapture;
            }

            p = CalculatePointPosition(p, origin);

            PageParameters? parameters = ComputePage(source, p, origin);
            _cornerPoint = p;
            if (parameters != null)
                ApplyParameters(parameters.Value);
        }

        private Point CalculatePointPosition(Point p, CornerOrigin origin)
        {
            if (!CanDragOutOfPage)
            {
                p.X = p.X > this.ActualWidth ? this.ActualWidth : p.X;
                p.X = p.X < 0 ? 0 : p.X;

                p.Y = p.Y > this.ActualHeight ? this.ActualHeight : p.Y;
                p.Y = p.Y < 0 ? 0 : p.Y;
            }

            //if (DragAngle != null)
            //{
            //    Point originPoint = new Point(0, 0);
            //    double angle = 0;

            //    switch (origin)
            //    {
            //        case CornerOrigin.TopLeft:
            //            originPoint = new Point(0, 0);
            //            angle = (Math.PI / 4) - (Math.Atan(p.X / p.Y));
            //            break;
            //        case CornerOrigin.TopRight:
            //            originPoint = new Point(ActualWidth, 0);
            //            break;
            //        case CornerOrigin.BottomLeft:
            //            originPoint = new Point(0, ActualHeight);
            //            break;
            //        case CornerOrigin.BottomRight:
            //            originPoint = new Point(ActualWidth, ActualHeight);
            //            break;
            //    }

            //    angle = Math.Abs(Math.Atan(Math.Abs(originPoint.X - p.X) / Math.Abs(originPoint.Y - p.Y)));

            //    var angleDegree = RadianToDegree(angle);

            //    double mini = 45 - DragAngle / 2;
            //    double max = 45 + DragAngle / 2;

            //    double hyp = Math.Sqrt((originPoint.X - p.X) * (originPoint.X - p.X) + (originPoint.Y - p.Y) * (originPoint.Y - p.Y));

            //    if (angleDegree > max || angleDegree < mini)
            //    {

            //        // coerce value
            //        if (angleDegree - max < mini - angleDegree)
            //        {
            //            // max is nearest
            //            p.X = Math.Cos(max) * hyp;
            //            p.Y = Math.Sin(max) * hyp;
            //        }
            //        else
            //        {
            //            // min is nearest
            //            p.X = Math.Cos(mini) * hyp;
            //            p.Y = Math.Sin(mini) * hyp;
            //        }
            //    }
            //}

            return p;
        }

        private double RadianToDegree(double angle)
        {
            return angle * (180.0 / Math.PI);
        }

        private void OnMouseOrContactDown(object sender, RoutedEventArgs args)
        {
            if ((Status == PageStatus.DropAnimation) || (Status == PageStatus.TurnAnimation) || IsBookBusy)
                return;

            UIElement source = sender as UIElement;

            bool isContact = false;
            Point p = new Point();

            if (args is ContactEventArgs)
            {
                // SURFACE
                var contactArgs = (ContactEventArgs)args;
                p = contactArgs.GetPosition(source);
                isContact = true;
                _currentDraggingContact = contactArgs.Contact;
            }
            else if (args is MouseButtonEventArgs)
            {
                p = ((MouseButtonEventArgs)args).GetPosition(source);
            }

            CornerOrigin? tmp = GetCorner(source, p);

            if (tmp.HasValue)
            {
                origin = tmp.Value;
                if (isContact)
                    ((ContactEventArgs)args).Contact.Capture(this);
                else
                    this.CaptureMouse();
            }
            else
                return;

            Status = PageStatus.Dragging;
        }

        private void OnMouseOrContactUp(object sender, RoutedEventArgs args)
        {
            bool isContactCaptured = args is ContactEventArgs ? ((ContactEventArgs)args).Contact.Captured == this : false;

            if (this.IsMouseCaptured || isContactCaptured)
            {
                Status = PageStatus.None;

                UIElement source = sender as UIElement;

                Point p = new Point();
                Point positionOrigin = new Point();

                if (args is ContactEventArgs)
                {
                    // SURFACE
                    var contactArgs = (ContactEventArgs)args;
                    p = contactArgs.GetPosition(source);
                    positionOrigin = contactArgs.GetPosition(this);
                }
                else if (args is MouseButtonEventArgs)
                {
                    var mouseArgs = (MouseButtonEventArgs)args;
                    p = mouseArgs.GetPosition(source);
                    positionOrigin = mouseArgs.GetPosition(this);
                }

                if (IsOnNextPage(positionOrigin, this, origin))
                    TurnPage(animationDuration);
                else
                    DropPage(ComputeAnimationDuration(source, p, origin));

                _currentDraggingContact = null;
                this.ReleaseMouseCapture();
            }
        }

        private void OnMouseOrContactLeave(object sender, RoutedEventArgs args)
        {
            if (Status == PageStatus.DraggingWithoutCapture)
            {
                //DropPage(ComputeAnimationDuration(source, p));
                DropPage(animationDuration);
            }
        }

        #endregion

        private Point OriginToPoint(UIElement source, CornerOrigin origin)
        {
            switch (origin)
            {
                case CornerOrigin.BottomLeft:
                    return new Point(0, source.RenderSize.Height);
                case CornerOrigin.BottomRight:
                    return new Point(source.RenderSize.Width, source.RenderSize.Height);
                case CornerOrigin.TopRight:
                    return new Point(source.RenderSize.Width, 0);
                default:
                    return new Point(0, 0);
            }
        }
        private Point OriginToOppositePoint(UIElement source, CornerOrigin origin)
        {
            switch (origin)
            {
                case CornerOrigin.BottomLeft:
                    return new Point(source.RenderSize.Width * 2, source.RenderSize.Height);
                case CornerOrigin.BottomRight:
                    return new Point(-source.RenderSize.Width, source.RenderSize.Height);
                case CornerOrigin.TopRight:
                    return new Point(-source.RenderSize.Width, 0);
                default:
                    return new Point(source.RenderSize.Width * 2, 0);
            }
        }
        private bool IsOnNextPage(Point p, UIElement source, CornerOrigin origin)
        {
            switch (origin)
            {
                case CornerOrigin.BottomLeft:
                case CornerOrigin.TopLeft:
                    return p.X > source.RenderSize.Width;
                default:
                    return p.X < 0;
            }
        }

        private void DropPage(int duration)
        {
            Status = PageStatus.DropAnimation;

            UIElement source = this as UIElement;
            CornerPoint = _cornerPoint;

            this.BeginAnimation(BookPage.CornerPointProperty, null);
            PointAnimation anim =
                new PointAnimation(
                    OriginToPoint(this, origin),
                    new Duration(TimeSpan.FromMilliseconds(duration)));
            anim.AccelerationRatio = 0.6;

            anim.CurrentTimeInvalidated += new EventHandler(anim_CurrentTimeInvalidated);
            anim.Completed += new EventHandler(anim_Completed);
            this.BeginAnimation(BookPage.CornerPointProperty, anim);
        }

        public void TurnPage()
        {
            TurnPage(animationDuration);
        }

        private void TurnPage(int duration)
        {
            Status = PageStatus.TurnAnimation;

            UIElement source = this as UIElement;
            CornerPoint = _cornerPoint;

            this.BeginAnimation(BookPage.CornerPointProperty, null);
            PointAnimation anim =
                new PointAnimation(
                    OriginToOppositePoint(this, origin),
                    new Duration(TimeSpan.FromMilliseconds(duration)));
            anim.AccelerationRatio = 0.6;

            anim.CurrentTimeInvalidated += new EventHandler(anim_CurrentTimeInvalidated);
            anim.Completed += new EventHandler(anim_Completed);
            this.BeginAnimation(BookPage.CornerPointProperty, anim);
        }

        internal void AutoTurnPage(CornerOrigin fromCorner, int duration, Action<int> AnimationFinishedCallBack, int argument)
        {
            if (Status != PageStatus.None)
                return;

            Status = PageStatus.TurnAnimation;

            UIElement source = this as UIElement;

            this.BeginAnimation(BookPage.CornerPointProperty, null);

            Point startPoint = OriginToPoint(this, fromCorner);
            Point endPoint = OriginToOppositePoint(this, fromCorner);

            origin = fromCorner;
            CornerPoint = startPoint;

            BezierSegment bs =
                new BezierSegment(startPoint, new Point(endPoint.X + (startPoint.X - endPoint.X) / 3, 250), endPoint, true);

            PathGeometry path = new PathGeometry();
            PathFigure figure = new PathFigure();
            figure.StartPoint = startPoint;
            figure.Segments.Add(bs);
            figure.IsClosed = false;
            path.Figures.Add(figure);

            PointAnimationUsingPath anim =
                new PointAnimationUsingPath();
            anim.PathGeometry = path;
            anim.Duration = new Duration(TimeSpan.FromMilliseconds(duration));
            anim.AccelerationRatio = 0.6;

            anim.CurrentTimeInvalidated += new EventHandler(anim_CurrentTimeInvalidated);

            anim.Completed += (s, e) =>
                {
                    // don't do anything if animation was breaked
                    if (Status == PageStatus.TurnAnimation)
                    {
                        ApplyParameters(new PageParameters(this.RenderSize));

                        if (AnimationFinishedCallBack != null)
                            AnimationFinishedCallBack.Invoke(argument);
                    }
                };

            this.BeginAnimation(BookPage.CornerPointProperty, anim);
        }

        internal void SetPage0PresenterVisibility(Visibility vis)
        {
            if (_page0 != null)
                _page0.Visibility = vis;
        }

        internal void SetPage1PresenterVisibility(Visibility vis)
        {
            if (_page1 != null)
                _page1.Visibility = vis;
        }

        internal void SetPage2PresenterVisibility(Visibility vis)
        {
            if (_page2 != null)
                _page2.Visibility = vis;
        }

        internal void StopAllAnimations()
        {
            this.BeginAnimation(BookPage.CornerPointProperty, null);
            Status = PageStatus.None;
        }

    }

    public delegate T Read<T>();
}
